package org.de.maeyer.statistic;

import static java.lang.Math.*;
import static org.de.maeyer.unit.UnitUtils.*;

import java.util.BitSet;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.de.maeyer.unit.Unit;

/**
 * @author Peter De Maeyer
 */
public class DefaultStatistics implements Statistics {
	
	static final int COUNT_BIT = 1;
	
	static final int LAST_VALUE_BIT = 2;
	
	static final int EXTREMA_BIT = 3;
	
	static final int TOTAL_BIT = 5;
	
	static final int ELAPSED_TIME_BIT = 6;
	
	static final int MEAN_BIT = 7;
	
	static final int VARIANCE_BIT = 8;
	
	static final int HISTOGRAM_BIT = 9;
	
	private final Class valueClass;
	
	private final String name;
	
	private final Unit unit;
	
	private final BitSet bits;
	
	private final long count;
	
	private final Unit timeUnit;
	
	private final long elapsedTime;
	
	private final Object lastValue;
	
	private final Object minimum;
	
	private final Object maximum;
	
	private final Number total;
	
	private final double mean;
	
	private final double residual;
	
	private final List<Category> categories;
	
	private final int[] modeCategoryIndexes;
	
	public DefaultStatistics(Class valueClass, String name, Unit unit, BitSet bits, long count, Object lastValue, Object minimum, Object maximum, Number total, Unit timeUnit, long elapsedTime, double mean, double residual, List<Category> categories, int[] modeCategoryIndexes) {
		this.valueClass = valueClass;
		this.name = name;
		this.unit = unit;
		this.bits = bits;
		this.count = count;
		this.lastValue = lastValue;
		this.minimum = minimum;
		this.maximum = maximum;
		this.total = total;
		this.timeUnit = timeUnit;
		this.elapsedTime = elapsedTime;
		this.mean = mean;
		this.residual = residual;
		this.categories = categories;
		this.modeCategoryIndexes = modeCategoryIndexes;
	}
	
	@Override
	public Class getValueClass() {
		return valueClass;
	}
	
	@Override
	public String getName() {
		return name;
	}
	
	@Override
	public Unit getUnit() {
	    return unit;
	}
	
	@Override
	public boolean hasCount() {
	    return bits.get(COUNT_BIT);
	}
	
	@Override
	public long getCount() {
		return count;
	}
	
	@Override
	public boolean hasLastValue() {
	    return bits.get(LAST_VALUE_BIT);
	}
	
	@Override
	public Object getLastValue() {
		return lastValue;
	}
	
	@Override
	public double getLastValue(Unit unit) {
	    return unit.convertFrom(((Number) lastValue).doubleValue(), getUnit()); 
	}
	
	@Override
	public boolean hasExtrema() {
	    return bits.get(EXTREMA_BIT);
	}
	
	@Override
	public Object getMinimum() {
	    return minimum;
	}
	
	@Override
	public double getMinimum(Unit unit) {
	    return unit.convertFrom(((Number) getMinimum()).doubleValue(), getUnit());
	}
	
	@Override
	public Object getMaximum() {
	    return maximum;
	}
	
	@Override
	public double getMaximum(Unit unit) {
		return unit.convertFrom(((Number) getMaximum()).doubleValue(), getUnit());
	}
	
	@Override
	public boolean hasTotal() {
	    return bits.get(TOTAL_BIT);
	}

	@Override
    public Number getTotal() {
		return total;
    }
	
	@Override
	public double getTotal(Unit unit) {
	    return unit.convertFrom(getTotal().doubleValue(), getUnit());
	}

	@Override
	public boolean hasElapsedTime() {
	    return bits.get(ELAPSED_TIME_BIT);
	}
	
	@Override
	public long getElapsedTime() {
		return elapsedTime;
	}
	
	@Override
	public double getElapsedTime(Unit timeUnit) {
	    return timeUnit.convertFrom(getElapsedTime(), getTimeUnit());
	}
	
	@Override
	public boolean hasMean() {
	    return bits.get(MEAN_BIT);
	}
	
	@Override
	public double getMean() {
	    return mean;
	}
	
	@Override
	public boolean hasVariance() {
	    return bits.get(VARIANCE_BIT);
	}
	
	@Override
	public double getPopulationStdDev() {
		return sqrt(getPopulationVariance());
	}
	
	@Override
	public double getPopulationVariance() {
		return residual / count;
	}
	
	@Override
	public double getSampleStdDev() {
		return sqrt(getSampleVariance());
	}
	
	@Override
	public double getSampleVariance() {
		return residual / (count - 1L);
	}
	
	@Override
	public boolean hasRate() {
	    return hasCount() && hasElapsedTime();
	}
	
	@Override
	public Unit getTimeUnit() {
		return timeUnit;
	}
	
	@Override
	public double getRate() {
		return (double) count / elapsedTime;
	}
	
	@Override
	public double getRate(Unit timeUnit) {
		return timeUnit.convertFrom(getRate(), getTimeUnit());
	}
	
	@Override
	public double getRate(TimeUnit timeUnit) {
		return getRate(unitFromTimeUnit(timeUnit));
	}
	
	@Override
	public boolean hasThroughput() {
	    return hasTotal() && hasElapsedTime();
	}
	
	@Override
	public double getThroughput() {
	    return total.doubleValue() / elapsedTime;
	}
	
	@Override
	public double getThroughput(Unit unit, Unit timeUnit) {
		return timeUnit.convertFrom(unit.convertFrom(getThroughput(), getUnit()), getTimeUnit());
	}
	
	@Override
	public double getThroughput(Unit unit, TimeUnit timeUnit) {
		return getThroughput(unit, unitFromTimeUnit(timeUnit));
	}
	
	@Override
	public boolean hasHistogram() {
	    return bits.get(HISTOGRAM_BIT);
	}
	
	@Override
	public List<Category> getCategories() {
	    return categories;
	}
	
	@Override
	public int[] getModeCategoryIndexes() {
	    return modeCategoryIndexes;
	}
	
	@Override
	public String toString() {
	    StringBuilder builder = new StringBuilder(1000);
	    builder.append(name).append('[');
	    if (unit != null) {
	    	builder.append("unit=").append(unit).append(", ");
	    }
	    if (hasCount()) {
	    	builder.append("count=").append(getCount()).append(", ");
	    }
	    if (hasLastValue()) {
	    	builder.append("lastValue=").append(getLastValue()).append(", ");
	    }
	    if (hasExtrema()) {
	    	builder.append("minimum=").append(getMinimum()).append(", ");
	    	builder.append("maximum=").append(getMaximum()).append(", ");
	    }
	    if (hasMean()) {
	    	builder.append("mean=").append(getMean()).append(", ");
	    }
	    if (hasVariance()) {
	    	builder.append("stdDev=").append(getSampleStdDev()).append(", ");
	    }
	    if (hasTotal()) {
	    	builder.append("total=").append(getTotal()).append(", ");
	    }
	    if (hasElapsedTime()) {
	    	builder.append("timeUnit=").append(timeUnit).append(", ");
	    	builder.append("elapsedTime=").append(getElapsedTime()).append(", ");
	    }
	    if (hasHistogram()) {
	    	List<Category> categories = getCategories();
	    	for (int i: getModeCategoryIndexes()) {
	    		builder.append("mode=").append(categories.get(i).getValue()).append(", ");
	    	}
	    }
	    if (builder.length() > name.length() + 1) {
	    	builder.setLength(builder.length() - 2);
	    }
	    builder.append(']');
	    return builder.toString();
	}
}
